//! 参见[`Board`]

use crate::{
    array2d::{Array2D, Slice2D},
    geometry::{Coord, Dir},
    level::Level,
};
use either::{Left, Right};
use itertools::Itertools;
#[cfg(test)]
use proptest::prelude::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{
    cell::Cell,
    fmt::Debug,
    hash::{Hash, Hasher},
    num::NonZeroU8,
};

/// 盘面的基本操作，如滑动、生成、加分。
/// 不包括动画、渲染、响应用户操作等。
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(serialize = "", deserialize = "E: Empty"))]
pub struct Board<'a, E> {
    score: u64,
    #[serde(serialize_with = "ser_cells", deserialize_with = "de_cells")]
    cells: Array2D<'a, Option<(Level, E)>>,
}

impl<E> Hash for Board<'_, E> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.score.hash(state);
        self.iter_cells().for_each(|ele| ele.hash(state));
    }
}

impl<E> PartialEq for Board<'_, E> {
    fn eq(&self, other: &Self) -> bool {
        self.score == other.score
            && self.size() == other.size()
            && self.iter_cells().eq(other.iter_cells())
    }
}
impl<E> Eq for Board<'_, E> {}

trait Empty: Default {}
impl Empty for () {}

fn ser_cells<E, S: Serializer>(
    cells: &Array2D<Option<(Level, E)>>,
    serializer: S,
) -> Result<S::Ok, S::Error> {
    cells.serialize_with(serializer, |cell| {
        cell.as_ref().map_or(0, |(level, _)| level.exp.get())
    })
}

fn de_cells<'a, 'de, E: Empty, D: Deserializer<'de>>(
    deserializer: D,
) -> Result<Array2D<'a, Option<(Level, E)>>, D::Error> {
    Array2D::deserialize_with(
        |level| {
            let exp = NonZeroU8::new(level)?;
            Some((Level { exp }, E::default()))
        },
        deserializer,
    )
}

pub trait World {
    type Block: Copy;
    fn spawn(&mut self, pos: Coord, level: Level) -> Self::Block;
    fn swipe(&mut self, block: Self::Block, dst: Coord, eliminate: bool);
}

impl<E: Copy> Board<'_, E> {
    /// 输入其尺寸。如果总面积为零，则返回空。
    pub fn new(size: Coord) -> Option<Self> {
        Some(Self {
            score: 0,
            cells: Array2D::new(size, None)?,
        })
    }

    /// 往`dir`方向滑动
    pub fn swipe(&mut self, dir: Dir, world: &mut impl World<Block = E>) {
        // 我不想用`RefCell`。为此，我精心设计了结构，将只依赖大小而不依赖内容的方法提取。
        // 于此，得以在局部使用`Cell`，使用迭代器漂亮地编程，并在全局保持漂亮的可变性，还没有开销。
        let dir = dir.reverse(); // 朝北划则聚于北，坐北而朝南
        let cells = self.cells.as_cells();
        for line in cells.enum_lines(dir) {
            let unwrap = |cell: &Cell<Option<_>>| cell.get().expect("格子为空");
            // 非空部分
            let compact = line.clone().map(|(_, c)| c).filter(|c| c.get().is_some());
            // 合并后
            let merge = compact.map(Left).coalesce(|pre, cur| {
                let pair = pre.left().zip(cur.left());
                pair.and_then(|(pre, cur)| {
                    let (level, _) = unwrap(pre);
                    (level == unwrap(cur).0).then_some(())?;
                    Some(Right((level.upgrade().expect("等级过高"), [pre, cur])))
                })
                .ok_or((pre, cur))
            });
            for (dst, src) in line.zip(merge) {
                match src {
                    Left(src) => {
                        world.swipe(unwrap(src).1, dst.0, false);
                        dst.1.swap(src);
                    }
                    Right((level, src)) => {
                        for src in &src {
                            let (level, block) = unwrap(src);
                            self.score += level.get();
                            world.swipe(block, dst.0, true);
                            src.set(None);
                        }
                        dst.1.set(Some((level, world.spawn(dst.0, level))));
                    }
                }
            }
        }
    }
}

impl<E> Board<'_, E> {
    pub fn score(&self) -> u64 {
        self.score
    }

    pub fn size(&self) -> Coord {
        self.cells.size()
    }

    pub fn get(&self, coord: Coord) -> Option<Level> {
        self.cells[coord].as_ref().map(|&(l, _)| l)
    }

    fn iter_coords(&self) -> impl Iterator<Item = Coord> {
        self.cells
            .walk_lines(Dir::Right /* 随意指定的方向 */)
            .flatten()
    }

    pub fn enum_lines(
        &self,
        dir: Dir,
    ) -> impl Iterator<Item = impl Iterator<Item = (Coord, Option<Level>)> + '_> + '_ {
        self.cells
            .enum_lines(dir)
            .map(|i| i.map(|(coord, cell)| (coord, cell.as_ref().map(|&(l, _)| l))))
    }

    pub fn enum_cells(&self) -> impl Iterator<Item = (Coord, Option<Level>)> + '_ {
        self.enum_lines(Dir::Right /* 随意指定的方向 */).flatten()
    }

    pub fn iter_lines(
        &self,
        dir: Dir,
    ) -> impl Iterator<Item = impl Iterator<Item = Option<Level>> + '_> + '_ {
        self.cells
            .iter_lines(dir)
            .map(|i| i.map(|c| c.as_ref().map(|&(l, _)| l)))
    }

    pub fn iter_cells(&self) -> impl Iterator<Item = Option<Level>> + '_ {
        self.cells
            .raw_data()
            .iter()
            .map(|c| c.as_ref().map(|&(l, _)| l))
    }

    pub fn possible_spawn(&self) -> impl Iterator<Item = ((Coord, Level), f64)> + '_ {
        self.enum_cells()
            .filter(|(_, cell)| cell.is_none())
            .flat_map(|(coord, _)| [(coord, 1, 0.9), (coord, 2, 0.1)])
            .map(|(coord, exp, pro)| {
                let exp = exp.try_into().unwrap();
                ((coord, Level { exp }), pro)
            })
    }

    pub fn do_spawn(&mut self, (coord, level): (Coord, Level), world: &mut impl World<Block = E>) {
        debug_assert!(self.cells[coord].is_none());
        self.cells[coord] = Some((level, world.spawn(coord, level)));
    }

    /// 判断滑动是否可行
    pub fn swipe_valid(&self, dir: Dir) -> bool {
        self.cells.iter_lines(dir).any(|line| {
            let line = line
                .map(|cell| cell.as_ref().map(|(l, _)| *l))
                .skip_while(Option::is_none);

            line.clone().any(|cell| cell.is_none()) // 有空位可去
            || line.flatten().tuple_windows().any(|(a, b)| a == b) // 能合并
        })
    }

    /// 判断能否继续游戏
    ///
    /// 如果局面为空，会返回真，但实际上无法继续
    pub fn cont(&self) -> bool {
        self.cells
            .iter_lines(Dir::Down)
            .chain(self.cells.iter_lines(Dir::Right))
            .any(|line| {
                line.map(Option::as_ref)
                    .tuple_windows()
                    .map(|(x, y)| Some(x?.0 == y?.0))
                    .any(|eq| eq.unwrap_or(true))
            })
    }

    pub fn clone_to<'a, W: World>(&self, world: &mut W) -> Board<'a, W::Block> {
        let mut board = Board::new(self.size()).unwrap();
        board.score = self.score;
        self.iter_coords()
            .filter_map(|coord| Some((coord, self.get(coord)?)))
            .for_each(|(coord, level)| {
                board.cells[coord] = Some((level, world.spawn(coord, level)));
            });
        board
    }

    pub fn cells(&self) -> Slice2D<Option<(Level, E)>> {
        self.cells.borrow()
    }
}

impl World for () {
    type Block = ();
    fn spawn(&mut self, _pos: Coord, _level: Level) -> Self::Block {}
    fn swipe(&mut self, _block: Self::Block, _dst: Coord, _eliminate: bool) {}
}

/// 由于带有生命周期泛型，无法 derive，只好手动实现
#[cfg(test)]
impl<E: Debug + Arbitrary + 'static> Arbitrary for Board<'_, E> {
    type Parameters = ();
    type Strategy = BoxedStrategy<Self>;
    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
        any::<(u64, Array2D<Option<(Level, E)>>)>()
            .prop_map(|(score, cells)| {
                let cells = cells.vary();
                Self { score, cells }
            })
            .boxed()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use itertools::Itertools;
    use serde_json::{from_str, to_string};

    fn new(exp: u8) -> Option<(Level, ())> {
        Some((
            Level {
                exp: exp.try_into().unwrap(),
            },
            (),
        ))
    }

    #[test]
    fn lines() {
        let board = Board {
            score: 0,
            cells: Array2D::from_iter([1, 2, 3, 4, 5, 6, 7, 8, 9].map(new).chunks(3)).unwrap(),
        };
        let asteq = |dir, res: [[_; 3]; 3]| {
            assert_eq!(
                board
                    .cells
                    .iter_lines(dir)
                    .map(|l| l.copied().collect_vec())
                    .collect_vec(),
                res.map(|l| l.map(new))
            );
        };
        asteq(Dir::Left, [[9, 8, 7], [6, 5, 4], [3, 2, 1]]);
        asteq(Dir::Right, [[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
        asteq(Dir::Up, [[9, 6, 3], [8, 5, 2], [7, 4, 1]]);
        asteq(Dir::Down, [[1, 4, 7], [2, 5, 8], [3, 6, 9]]);
    }

    #[test]
    fn swipe() {
        let mut board = Board {
            score: 0,
            cells: Array2D::from_iter(
                [
                    new(1),
                    None,
                    new(1),
                    new(2),
                    new(2),
                    new(2),
                    new(3),
                    new(3),
                    None,
                    new(3),
                    new(4),
                    new(3),
                ]
                .chunks(3),
            )
            .unwrap(),
        };
        board.swipe(Dir::Right, &mut ());
        assert_eq!(
            board.cells.raw_data(),
            [
                None,
                None,
                new(2),
                None,
                new(2),
                new(3),
                None,
                None,
                new(4),
                new(3),
                new(4),
                new(3),
            ]
        );
    }

    #[test]
    fn cont() {
        let cont = |cells: [_; 4]| {
            Board {
                score: 0,
                cells: Array2D::from_iter(cells.chunks(2)).unwrap(),
            }
            .cont()
        };
        assert!(cont([new(1), None, new(2), new(3)]));
        assert!(cont([new(1), new(1), new(2), new(3)]));
        assert!(cont([new(1), new(2), new(3), new(2)]));
        assert!(!cont([new(1), new(2), new(2), new(1)]));
        assert!(!cont([new(1), new(2), new(3), new(4)]));
    }

    proptest!(
        #[test]
        fn cont_fuzz(mut board: Board<'_, ()>) {
            let mut dirs = Dir::EACH.into_iter();
            if board.cont() {
                let empty = board.cells.raw_data().iter().all(Option::is_none);
                let can_swipe = dirs.any(|dir| board.swipe_valid(dir));
                prop_assert!(empty ^ can_swipe);
            } else {
                for dir in dirs {
                    prop_assert!(!board.swipe_valid(dir));
                }
            }
        }

        #[test]
        fn swipe_fuzz(mut board: Board<'_, ()>, dir: Dir) {
            let board1 = board.clone();
            if board.swipe_valid(dir) {
                board.swipe(dir, &mut ());
                prop_assert_ne!(board, board1);
            } else {
                board.swipe(dir, &mut ());
                prop_assert_eq!(board, board1);
            }
        }

        #[test]
        fn spawn_fuzz(board: Board<'_, ()>) {
            let blank = board.iter_cells().filter(Option::is_none).count();
            prop_assert_eq!(board.possible_spawn().count(), 2 * blank);
            for ((coord, level), p) in board.possible_spawn() {
                prop_assert!(p > 0.0);
                let mut board2 = board.clone();
                board2.do_spawn((coord, level), &mut ());
                let blank2 = board2.iter_cells().filter(Option::is_none).count();
                prop_assert_eq!(blank2, blank - 1);
            }
        }

        #[test]
        fn clone_fuzz(board: Board<'_, ()>) {
            prop_assert_eq!(board.clone_to(&mut ()), board);
        }

        #[test]
        fn serde_fuzz(board: Board<'_, ()>) {
            prop_assert_eq!(
                from_str::<Board<'_, ()>>(dbg!(&to_string(&board).unwrap())).unwrap(),
                board
            );
        }
    );
}
